iT邦幫忙

2024 iThome 鐵人賽

DAY 15
0
Modern Web

Laravel 那麼好用還需要自幹框架嗎系列 第 15

Day 15:\Illuminate\Database\Query\Builder get()

  • 分享至 

  • xImage
  •  

昨天我們看過了

DB::table('user')->get();

裡面的 table() 函數是怎麼取得 \Illuminate\Database\Query\Builder 物件。今天我們來看看 get() 底層的實作,以及怎麼取出一個 Laravel Collection。

public function get($columns = ['*'])
{
	$items = collect($this->onceWithColumns(Arr::wrap($columns), function () {
		return $this->processor->processSelect($this, $this->runSelect());
	}));

	return $this->applyAfterQueryCallbacks(
		isset($this->groupLimit) ? $this->withoutGroupLimitKeys($items) : $items
	);
}

首先我們看到 onceWithColumns()

/**
 * Execute the given callback while selecting the given columns.
 *
 * After running the callback, the columns are reset to the original value.
 *
 * @param  array  $columns
 * @param  callable  $callback
 * @return mixed
 */
protected function onceWithColumns($columns, $callback)
{
	$original = $this->columns;

	if (is_null($original)) {
		$this->columns = $columns;
	}

	$result = $callback();

	$this->columns = $original;

	return $result;
}

這邊會整理出所有的 column 資訊,我們這邊沒有特別指定,所以是預設的 ['*']

接著我們看到 processSelect()

public function processSelect(Builder $query, $results)
{
	return $results;
}

沒什麼可看的,看起來實際解析的邏輯不在這邊。那麼就應該在 runSelect() 裡面了!

/**  
 * Run the query as a "select" statement against the connection. * * @return array  
 */protected function runSelect()  
{  
    return $this->connection->select(  
        $this->toSql(), $this->getBindings(), ! $this->useWritePdo  
    );  
}

根據函數名稱來看,這邊開始就會深入到取得 SQL 語法的地方了

我們先來看看 toSql()

/**
 * Get the SQL representation of the query.
 *
 * @return string
 */
public function toSql()
{
	$this->applyBeforeQueryCallbacks();

	return $this->grammar->compileSelect($this);
}

這邊我們可以看到,這邊的語法會使用 $this->grammar 進行分析。我們往下看 $this->grammar->compileSelect($this)

/**
 * Compile a select query into SQL.
 *
 * @param  \Illuminate\Database\Query\Builder  $query
 * @return string
 */
public function compileSelect(Builder $query)
{
	if (($query->unions || $query->havings) && $query->aggregate) {
		return $this->compileUnionAggregate($query);
	}

	// If a "group limit" is in place, we will need to compile the SQL to use a
	// different syntax. This primarily supports limits on eager loads using
	// Eloquent. We'll also set the columns if they have not been defined.
	if (isset($query->groupLimit)) {
		if (is_null($query->columns)) {
			$query->columns = ['*'];
		}

		return $this->compileGroupLimit($query);
	}

	// If the query does not have any columns set, we'll set the columns to the
	// * character to just get all of the columns from the database. Then we
	// can build the query and concatenate all the pieces together as one.
	$original = $query->columns;

	if (is_null($query->columns)) {
		$query->columns = ['*'];
	}

	// To compile the query, we'll spin through each component of the query and
	// see if that component exists. If it does we'll just call the compiler
	// function for the component which is responsible for making the SQL.
	$sql = trim($this->concatenate(
		$this->compileComponents($query))
	);

	if ($query->unions) {
		$sql = $this->wrapUnion($sql).' '.$this->compileUnions($query);
	}

	$query->columns = $original;

	return $sql;
}

這邊我們沒有 $query->unions$query->havings$query->aggregate,所以會一路往下,到 $query->columns = ['*']

接著我們看看 compileComponents()

/**  
 * Compile the components necessary for a select clause. * * @param  \Illuminate\Database\Query\Builder  $query  
 * @return array  
 */protected function compileComponents(Builder $query)  
{  
    $sql = [];  
  
    foreach ($this->selectComponents as $component) {  
        if (isset($query->$component)) {  
            $method = 'compile'.ucfirst($component);  
  
            $sql[$component] = $this->$method($query, $query->$component);  
        }  
    }  
  
    return $sql;  
}

這段會根據 $query->$component 是否存在,呼叫對應的函數。

不過這邊選用了對函數命名做呼叫的方式進行篩選。

由於我們有設置 $query->columns$query->from,所以我們會呼叫到 compileColumns()compileFrom() 兩個函數。

這兩個函數會提供我們所需的 sql 片段

/**  
 * Compile the "select *" portion of the query. * * @param  \Illuminate\Database\Query\Builder  $query  
 * @param  array  $columns  
 * @return string|null  
 */protected function compileColumns(Builder $query, $columns)  
{  
    // If the query is actually performing an aggregating select, we will let that  
    // compiler handle the building of the select clauses, as it will need some    // more syntax that is best handled by that function to keep things neat.    if (! is_null($query->aggregate)) {  
        return;  
    }  
  
    if ($query->distinct) {  
        $select = 'select distinct ';  
    } else {  
        $select = 'select ';  
    }  
  
    return $select.$this->columnize($columns);  
}
/**
 * Compile the "from" portion of the query.
 *
 * @param  \Illuminate\Database\Query\Builder  $query
 * @param  string  $table
 * @return string
 */
protected function compileFrom(Builder $query, $table)
{
	return 'from '.$this->wrapTable($table);
}

取出片段之後,會在 $this->concatenate() 裡面進行組合

/**  
 * Concatenate an array of segments, removing empties. * * @param  array  $segments  
 * @return string  
 */protected function concatenate($segments)  
{  
    return implode(' ', array_filter($segments, function ($value) {  
        return (string) $value !== '';  
    }));  
}

組合之後,再經過 trim(),就可以生成完整的 SQL 語句了!

我們繼續看 $this->connection->select()

/**  
 * Run a select statement against the database. * * @param  string  $query  
 * @param  array  $bindings  
 * @param  bool  $useReadPdo  
 * @return array  
 */public function select($query, $bindings = [], $useReadPdo = true)  
{  
    return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {  
        if ($this->pretending()) {  
            return [];  
        }  
  
        // For select statements, we'll simply execute the query and return an array  
        // of the database result set. Each element in the array will be a single        // row from the database table, and will either be an array or objects.        $statement = $this->prepared(  
            $this->getPdoForSelect($useReadPdo)->prepare($query)  
        );  
  
        $this->bindValues($statement, $this->prepareBindings($bindings));  
  
        $statement->execute();  
  
        return $statement->fetchAll();  
    });  
}

run() 的實作則是

/**  
 * Run a SQL statement and log its execution context. * * @param  string  $query  
 * @param  array  $bindings  
 * @param  \Closure  $callback  
 * @return mixed  
 * * @throws \Illuminate\Database\QueryException  
 */protected function run($query, $bindings, Closure $callback)  
{  
    foreach ($this->beforeExecutingCallbacks as $beforeExecutingCallback) {  
        $beforeExecutingCallback($query, $bindings, $this);  
    }  
  
    $this->reconnectIfMissingConnection();  
  
    $start = microtime(true);  
  
    // Here we will run this query. If an exception occurs we'll determine if it was  
    // caused by a connection that has been lost. If that is the cause, we'll try    // to re-establish connection and re-run the query with a fresh connection.    try {  
        $result = $this->runQueryCallback($query, $bindings, $callback);  
    } catch (QueryException $e) {  
        $result = $this->handleQueryException(  
            $e, $query, $bindings, $callback  
        );  
    }  
  
    // Once we have run the query we will calculate the time that it took to run and  
    // then log the query, bindings, and execution time so we will report them on    // the event that the developer needs them. We'll log time in milliseconds.    $this->logQuery(  
        $query, $bindings, $this->getElapsedTime($start)  
    );  
  
    return $result;  
}

到這邊,我們就看過了在 DB::table('user')->get(); 裡面,Laravel 是怎麼建立出 SQL Query,並且利用 PDO 物件對資料庫進行存取的。


上一篇
Day 14:Query Builder 的 DB::table()
下一篇
Day 16:DAO 轉換
系列文
Laravel 那麼好用還需要自幹框架嗎18
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言